package demo.advice;

import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import demo.cache.CacheableMethod;
import demo.cache.CallCache;
import demo.cache.ObjectMethodCall;

@Component
@Aspect
public class CachingAdvice implements Ordered {
    private static final Logger log = Logger.getLogger(CachingAdvice.class);

    // Have the caching occur first, before transactions are started, etc.
    private int order = 1;
    
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
    
    private static final CallCache GLOBAL_CACHE = new CallCache(true);
    

    @Around("execution(* demo.*ServiceImpl.*(..))")
    public Object checkCache(ProceedingJoinPoint pjp)
            throws Throwable {
        Object toReturn = null;
        MethodInvocationProceedingJoinPoint mipjp = (MethodInvocationProceedingJoinPoint) pjp;
        MethodSignature signature = (MethodSignature) mipjp.getSignature();
        final Object[] args = mipjp.getArgs();
        final Method method = signature.getMethod();
        final Object target = mipjp.getTarget();
        final ObjectMethodCall call = new ObjectMethodCall(target, method, args);
        boolean globallyCacheableAndUncached = method.getAnnotation(CacheableMethod.class) != null;
        if (globallyCacheableAndUncached) {
            toReturn = GLOBAL_CACHE.getFromCache(call);
            if (toReturn != null) {
                log.info("Global cache hit for " + 
                               target.getClass().getSimpleName() + "." + method.getName());
                globallyCacheableAndUncached = false;
            }
        }
        if (toReturn == null) {
            toReturn = pjp.proceed();
        }
        if (globallyCacheableAndUncached) {
            GLOBAL_CACHE.setInCache(call, toReturn);
        }
        if (toReturn == CallCache.NULL_RETURN) {
            toReturn = null;
        }        
        return toReturn;
    }

}
